using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Services;
using MainGame.GameSound;
using MainGame.SaveData;

namespace MainGame.GamePlay
{
  using PartialScores = MainGame.StateManagement.PartialScores;

  /// <summary>
  /// Gestiamo la logica del giocatore, ascoltando l'evento di fine turno e
  /// i clicks su una carta
  /// </summary>
  public abstract class PlayerLayer : AILayer
  {

    public PlayerLayer(Game game)
      : base(game)
    {
      UpdateOrder = (int)ComponentOrders.LOGIC;
    }

    public override void Initialize()
    {
      Game.GetBoardRenderer().ClickPlayerCard += new ClickPlayerCard(BoardManager_ClickPlayerCard);
      Game.GetBoardRenderer().ReleasePlayerCard += new ReleasePlayerCard(BoardManager_ReleasePlayerCard);
      Game.GetBoardRenderer().ClickPack += new ClickPack(BoardManager_ClickPack); 
      Game.GetUIRenderer().OnTurnOver += new OnTurnOver(BoardManager_OnTurnOver);

      base.Initialize();
    }

    protected virtual void BoardManager_ClickPlayerCard(int player_index, int card_index)
    {
      var b = players[player_index].Cards[card_index].Selected;
      //players[player_index].Cards.ForEach(c => c.Selected = false);
      players[player_index].Cards[card_index].Selected = !b;
      var sound_manager = Game.Services.GetService(typeof(ISoundManager<GamePlaySounds>)) as ISoundManager<GamePlaySounds>;
      sound_manager.PlaySound((GamePlaySounds)random.Next((int)GamePlaySounds.CardFlip1, (int)GamePlaySounds.CardFlip5 + 1));
    }

    protected virtual void BoardManager_ReleasePlayerCard(int player_index, int card_index, bool sx)
    {
        players[player_index].Cards.ForEach(c => c.Selected = false);
        //var sound_manager = Game.Services.GetService(typeof(ISoundManager<GamePlaySounds>)) as ISoundManager<GamePlaySounds>;
    }

    /// <summary>
    /// Estrae nuove carte dal mazzo e le assegna al giocatore
    /// </summary>
    /// <param name="player_index">L'indice del giocatore</param>
    /// <param name="card_quantity">Il numero di carte da estrarre</param>
    protected virtual void BoardManager_ClickPack(int player_index, int card_quantity)
    {
      if (pack.Cards.Count() > 0)
      {
        for (int i = 0; i < card_quantity; ++i)
        {
          // Move card from pack to player
          players[player_index].Cards.Add(pack.Cards.First());
          pack.Cards = pack.Cards.Skip(1).ToList();
          // Play sound
          var soundManager = Game.Services.GetService(typeof(ISoundManager<GamePlaySounds>)) as ISoundManager<GamePlaySounds>;
          soundManager.PlaySound((GamePlaySounds)random.Next((int)GamePlaySounds.PackPick1, (int)GamePlaySounds.PackPick3 + 1));
        }
      }

      // Check if the victory/loss conditions are met
      CheckVictory(player_index);
    }


    /// <summary>
    /// Checks if the victory/loss conditions are met for this game
    /// </summary>
    /// <param name="player_index">The index of the player to check the conditions for</param>
    protected void CheckVictory(int player_index)
    {
      // The pack has to be empty in all cases
      if (Pack.Cards.Count != 0)
        return;

      // Victory conditions: no possible moves and exactly 2 cards/packs on table
      if (players[player_index].MossePossibili() == 0 && players[player_index].Cards.Count() == 2)
      {
        //players[player_index].Score = ComputeScore(player_index);
        //score.PlayerScores = (from p in players
        //                      select new PlayerScore()
        //                      {
        //                        Name = p.Name,
        //                        Score = p.Score,
        //                      }).ToList();
        //SavePartialScore(score);
        //// Eventually add the score to the high score chart
        //AddToChart(player_index);
        // Save stats in the player's profile
        Game.GetContext().currentProfile.gamesPlayed++;
        Game.GetContext().currentProfile.gamesWon++;
        var ui_renderer = Game.Services.GetService(typeof(IUIRenderer)) as IUIRenderer;
        Game.GetContext().currentProfile.timePlayed += (double)ui_renderer.elapsedTime;
        // Call game end event (victory)
        LevelWon(score);
      }

      // Loss conditions: no possible moves but more than two cards/packs on table
      if (players[player_index].MossePossibili() == 0 && players[player_index].Cards.Count() > 2)
      {
        ////LevelLost();
        //players[player_index].Score = ComputeScore(player_index);
        //score.PlayerScores = (from p in players
        //                      select new PlayerScore()
        //                      {
        //                        Name = p.Name,
        //                        Score = p.Score,
        //                      }).ToList();
        //// Save the player's score even in this case
        //SavePartialScore(score);
        //// Eventually add the score to the high score chart
        //AddToChart(player_index);
        // Save stats in the player's profile
        Game.GetContext().currentProfile.gamesPlayed++;
        var ui_renderer = Game.Services.GetService(typeof(IUIRenderer)) as IUIRenderer;
        Game.GetContext().currentProfile.timePlayed += (double)ui_renderer.elapsedTime;
        // Call game end event (again, victory, to mantain score)
        LevelWon(score);
      }
    }

    /// <summary>
    /// Finalize game: compute the score, check if it's a high-score
    /// </summary>
    /// <param name="player_index">The index of the player to compute the score for</param>
    public void FinalizeGame()
    {
      players[0].Score = ComputeScore(0);
      score.PlayerScores = (from p in players
                            select new PlayerScore()
                            {
                              Name = p.Name,
                              Score = p.Score,
                            }).ToList();
      SavePartialScore(score);
      // Eventually add the score to the high score chart
      AddToChart(0);
    }

    /// <summary>
    /// Compute the score for a specified player
    /// </summary>
    /// <param name="player_index">The index of the player to compute the score for</param>
    /// <returns>The total score for this player</returns>
    protected int ComputeScore(int player_index)
    {
      GameDifficulty difficulty = Game.GetContext().currentConfig.difficulty;
      // Calcola il fattore difficolta'
      float difficultyFactor = 1.0f + ((int)difficulty * 1.0f);   // Easy = 1, Normal = 2, Hard = 3
      int cardsNumber = Math.Min(6, players[player_index].Cards.Count());  // Considera solo le carte entro le prime sei posizioni
      int[] cardFactors = new int[cardsNumber];
      int[] cardTotals = new int[cardsNumber];
      var board_renderer = Game.Services.GetService(typeof(IBoardRenderer)) as BoardRenderer;
      int[] mazzetti = board_renderer.mazzetto;
      for (int i = 0; i < cardsNumber; ++i)
      {
        // Easy: 5 5 4 3 2 1
        // Normal: 10 10 8 6 4 2
        // Hard: 15 15 12 9 6 3
        cardFactors[i] = (int)((6 - i) * difficultyFactor);
        if (i == 0)
          --cardFactors[i];
        cardTotals[i] = cardFactors[i] * mazzetti[i];
      }

      // Calcola bonus mazzetto piu' grande
      int bonusMazzetto = 0;
      for (int i = 0; i < mazzetti.Count(); ++i)
        bonusMazzetto = Math.Max(bonusMazzetto, mazzetti[i]);

      // Calcola punteggio totale
      int totalScore = 0;
      for (int i = 0; i < cardsNumber; ++i)
        totalScore += cardTotals[i];
      totalScore += (int)(bonusMazzetto * difficultyFactor);

      return totalScore;
    }

    /// <summary>
    /// Eventually adds a high-score to the high-scores chart
    /// </summary>
    /// <param name="player_index">The index of the player to check the score for</param>
    protected void AddToChart(int player_index)
    {
      var ui_renderer = Game.Services.GetService(typeof(IUIRenderer)) as UIRenderer;
      ChartEntry newEntry = new ChartEntry()
      {
        username = players[player_index].Name,
        score = players[player_index].Score,
        difficulty = Game.GetContext().currentConfig.difficulty,
        time = ui_renderer.elapsedTime
      };

      SaveData.Chart chart = Game.GetContext().currentChart;

      chart.entries.Add(newEntry);
      chart.entries = chart.entries.OrderByDescending(e => e.score).ToList();
      if (chart.entries.Count > 10)
        chart.entries.Remove(chart.entries.Last());

      // Update chart in memory
      Game.GetContext().currentChart = chart;
    }

    /// <summary>
    /// Salva il punteggio di tutti i giocatori per il round corrente
    /// </summary>
    /// <param name="score">I punteggi da salvare</param>
    protected void SavePartialScore(Score score)
    {
      if (PartialScores.scoreList == null)
        PartialScores.scoreList = new List<Score>();
      PartialScores.scoreList.Add(score);

      // Save best score
      int humanPlayerScore = score.PlayerScores[0].Score;
      Game.GetContext().currentProfile.bestScore = Math.Max(humanPlayerScore, Game.GetContext().currentProfile.bestScore);
    }

    void BoardManager_OnTurnOver()
    {
      if (CurrentPlayer.IsHuman)
      {
        if (CurrentPlayer.Cards.Any(c => c.Selected))
        {
          var card = CurrentPlayer.Cards.First(c => c.Selected);
          if (blocks.Any() && blocks.First().Cards.Any())
          {
            if (card.Color != blocks.First().Cards.First().Color)
              return;
          }
          CurrentPlayer.Cards.Remove(card);
          blocks.Add(new CardBlock() { Cards = new List<Card>() { card }, PlayedBy = CurrentPlayer });
          TurnOver();
        }
      }
    }
  }
}
